/**
 * 文件名：SerialTool.java
 *
 * 创建人：Elvin Zhao - 279152260@qq.com
 *
 * 创建时间：2018年7月10日 下午3:30:32
 *
 * 版权所有：FNII
 */
package com.springvision.test;

/**
 * [描述信息：说明类的基本功能]
 *
 * @author Elvin Zhao - 279152260@qq.com
 * @version 1.0 Created on 2018年7月10日 下午3:30:32
 */
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**
 * 串口服务类，提供打开、关闭串口，读取、发送串口数据等服务（采用单例设计模式）
 * @author zhong
 *
 */
public class SerialTool {

	private static SerialTool serialTool = null;

	static {
		// 在该类被ClassLoader加载时就初始化一个SerialTool对象
		if (serialTool == null) {
			serialTool = new SerialTool();
		}
	}

	// 私有化SerialTool类的构造方法，不允许其他类生成SerialTool对象
	private SerialTool() {
	}

	/**
	 * 获取提供服务的SerialTool对象
	 * @return serialTool
	 */
	public static SerialTool getSerialTool() {
		if (serialTool == null) {
			serialTool = new SerialTool();
		}
		return serialTool;
	}

	/**
	 * 查找所有可用端口
	 * @return 可用端口名称列表
	 */
	public static final ArrayList<String> findPort() {

		// 获得当前所有可用串口
		@SuppressWarnings("unchecked")
		Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();

		ArrayList<String> portNameList = new ArrayList<>();

		// 将可用串口名添加到List并返回该List
		while (portList.hasMoreElements()) {
			String portName = portList.nextElement().getName();
			portNameList.add(portName);
		}

		return portNameList;

	}

	/**
	 * 打开串口
	 * @param portName 端口名称
	 * @param baudrate 波特率
	 * @return 串口对象
	 * @throws NoSuchPort 设置串口参数失败
	 * @throws NotASerialPort 端口指向设备不是串口类型
	 * @throws NoSuchPort 没有该端口对应的串口设备
	 * @throws PortInUse 端口已被占用
	 */
	public static final SerialPort openPort(String portName, int baudrate)
			throws NoSuchPort, NotASerialPort, NoSuchPort, PortInUse {

		try {

			// 通过端口名识别端口
			CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);

			// 打开端口，并给端口名字和一个timeout（打开操作的超时时间）
			CommPort commPort = portIdentifier.open(portName, 2000);

			// 判断是不是串口
			if (commPort instanceof SerialPort) {

				SerialPort serialPort = (SerialPort) commPort;

				try {
					// 设置一下串口的波特率等参数
					serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
							SerialPort.PARITY_NONE);
				} catch (UnsupportedCommOperationException e) {
					throw new NoSuchPort();
				}

				// System.out.println("Open " + portName + " sucessfully !");
				return serialPort;

			} else {
				// 不是串口
				throw new NotASerialPort();
			}
		} catch (NoSuchPortException e1) {
			throw new NoSuchPort();
		} catch (PortInUseException e2) {
			throw new PortInUse();
		}
	}

	/**
	 * 关闭串口
	 * @param serialport 待关闭的串口对象
	 */
	public static void closePort(SerialPort serialPort) {
		if (serialPort != null) {
			serialPort.close();
			serialPort = null;
		}
	}

	/**
	 * 往串口发送数据
	 * @param serialPort 串口对象
	 * @param order    待发送数据
	 * @throws SendDataToSerialPortFailure 向串口发送数据失败
	 * @throws SerialPortOutputStreamCloseFailure 关闭串口对象的输出流出错
	 */
	public static void sendToPort(SerialPort serialPort, byte[] order)
			throws SendDataToSerialPortFailure, SerialPortOutputStreamCloseFailure {

		OutputStream out = null;

		try {

			out = serialPort.getOutputStream();
			out.write(order);
			out.flush();

		} catch (IOException e) {
			throw new SendDataToSerialPortFailure();
		} finally {
			try {
				if (out != null) {
					out.close();
					out = null;
				}
			} catch (IOException e) {
				throw new SerialPortOutputStreamCloseFailure();
			}
		}

	}

	/**
	 * 从串口读取数据
	 * @param serialPort 当前已建立连接的SerialPort对象
	 * @return 读取到的数据
	 * @throws ReadDataFromSerialPortFailure 从串口读取数据时出错
	 * @throws SerialPortInputStreamCloseFailure 关闭串口对象输入流出错
	 */
	public static byte[] readFromPort(SerialPort serialPort)
			throws ReadDataFromSerialPortFailure, SerialPortInputStreamCloseFailure {

		InputStream in = null;
		byte[] bytes = null;

		try {

			in = serialPort.getInputStream();
			int bufflenth = in.available(); // 获取buffer里的数据长度

			while (bufflenth != 0) {
				bytes = new byte[bufflenth]; // 初始化byte数组为buffer中数据的长度
				in.read(bytes);
				bufflenth = in.available();
			}
		} catch (IOException e) {
			throw new ReadDataFromSerialPortFailure();
		} finally {
			try {
				if (in != null) {
					in.close();
					in = null;
				}
			} catch (IOException e) {
				throw new SerialPortInputStreamCloseFailure();
			}

		}

		return bytes;

	}

	/**
	 * 添加监听器
	 * @param port     串口对象
	 * @param listener 串口监听器
	 * @throws TooManyListeners 监听类对象过多
	 */
	public static void addListener(SerialPort port, SerialPortEventListener listener) throws TooManyListeners {

		try {

			// 给串口添加监听器
			port.addEventListener(listener);
			// 设置当有数据到达时唤醒监听接收线程
			port.notifyOnDataAvailable(true);
			// 设置当通信中断时唤醒中断线程
			port.notifyOnBreakInterrupt(true);

		} catch (TooManyListenersException e) {
			throw new TooManyListeners();
		}
	}

	public static void main(String[] args) {
		 ArrayList<String> commList = SerialTool.findPort(); // 程序初始化时就扫描一次有效串口
		 System.err.println("find port:" + commList.toString());

		// serialPort = SerialTool.openPort(commName, bps);

		// 获取串口名称
		String commName = "COM4";
		// 获取波特率
		Integer bps = 9600;

		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			System.err.println("-----------------------------------------------------");
			// 获取指定端口名及波特率的串口对象
			SerialPort serialPort = null;
			try {
				serialPort = SerialTool.openPort(commName, bps);

				SerialListener listener = new SerialListener(serialPort);
				SerialTool.addListener(serialPort, listener);

			} catch (NoSuchPort | NotASerialPort | PortInUse | TooManyListeners e) {
				e.printStackTrace();
			}finally {
				SerialTool.closePort(serialPort);
			}
			// 在该串口对象上添加监听器
			// 监听成功进行提示
		}
	}

}